﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;
using System.Collections;
using System.IO;

namespace Xin478.MySocket
{
    /// <summary> 
    /// 提供Tcp网络连接服务的客户端类 
    /// 
    /// 原理: 
    /// 1.使用异步Socket通讯与服务器按照一定的通讯格式通讯,请注意与服务器的通 
    /// 讯格式一定要一致,否则可能造成服务器程序崩溃,整个问题没有克服,怎么从byte[] 
    /// 判断它的编码格式 
    /// 2.支持带标记的数据报文格式的识别,以完成大数据报文的传输和适应恶劣的网 
    /// 络环境. 
    /// </summary> 
    public class TcpCli 
    {
        #region 字段

        /// <summary> 
        /// 客户端与服务器之间的会话类 
        /// </summary> 
        private Session _session;

        /// <summary> 
        /// 客户端是否已经连接服务器 
        /// </summary> 
        private bool _isConnected = false;

        /// <summary> 
        /// 接收数据缓冲区大小
        /// </summary> 
        public const int BUFFERSIZE = 32 * 1024;

        /// <summary> 
        /// 接收数据缓冲区 
        /// </summary> 
        private byte[] _recvDataBuffer = new byte[BUFFERSIZE];

        private IDatagramAnalyze _datagramAnalyze;

        #endregion

        #region 事件定义

        public delegate void SockErrorEventHandler(string errString);
        public event SockErrorEventHandler OnSockErrorEvent;

        //需要订阅事件才能收到事件的通知，如果订阅者退出，必须取消订阅 

        /// <summary> 
        /// 已经连接服务器事件 
        /// </summary> 
        public event NetEventHandler OnConnectedServerEvent;

        /// <summary> 
        /// 接收到数据报文事件 
        /// </summary> 
        public event NetEventHandler OnReceivedDatagramEvent;

        /// <summary> 
        /// 连接断开事件 
        /// </summary> 
        public event NetEventHandler OnDisConnectedServerEvent;
        #endregion

        #region 属性
        public IDatagramAnalyze DatagramAnalyze
        {
            set { this._datagramAnalyze = value as IDatagramAnalyze; }
            get { return this._datagramAnalyze as IDatagramAnalyze; }
        }

        /// <summary> 
        /// 返回客户端与服务器之间的会话对象 
        /// </summary> 
        public Session ClientSession
        {
            get
            {
                return _session;
            }
        }

        /// <summary> 
        /// 返回客户端与服务器之间的连接状态 
        /// </summary> 
        public bool IsConnected
        {
            get
            {
                return _isConnected;
            }
        }
        #endregion

        #region 公有方法

        /// <summary> 
        /// 默认构造函数,使用默认的编码格式 
        /// </summary> 
        public TcpCli() {}

        /// <summary> 
        /// 连接服务器 
        /// </summary> 
        /// <param name="ip">服务器IP地址</param> 
        /// <param name="port">服务器端口</param> 
        public virtual void Connect(string ip, int port)
        {
            if (IsConnected)
            {              
                //重新连接 
                Debug.Assert(_session != null);

                Close();
            }

            Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            
            IPEndPoint iep = new IPEndPoint(IPAddress.Parse(ip), port);
            newsock.BeginConnect(iep, new AsyncCallback(Connected), newsock);
        }

        /// <summary> 
        /// 发送数据报文 
        /// </summary> 
        /// <param name="datagram"></param> 
        public virtual void Send(byte[] datagram)
        {
            if (datagram == null) return;
            if (!_isConnected) throw new ApplicationException("没有连接服务器，不能发送数据");

            datagram = this._datagramAnalyze.Assemble(datagram);

            //获得报文的编码字节 
            _session.ClientSocket.BeginSend(datagram, 0, datagram.Length, SocketFlags.None, new AsyncCallback(SendDataEnd), _session.ClientSocket);
        }

        /// <summary> 
        /// 关闭连接 
        /// </summary> 
        public virtual void Close()
        {
            if (!_isConnected)
            {
                return;
            }

            if (OnDisConnectedServerEvent != null)
            {
                OnDisConnectedServerEvent(this, new NetEventArgs(_session));
            }

            _session.Close();

            _session = null;

            _isConnected = false;
        }

        #endregion

        #region 受保护方法

        /// <summary> 
        /// 数据发送完成处理函数 
        /// </summary> 
        /// <param name="iar"></param> 
        protected virtual void SendDataEnd(IAsyncResult iar)
        {
            Socket remote = (Socket)iar.AsyncState;
            int sent= remote.EndSend(iar);
            Debug.Assert(sent != 0);
        }

        /// <summary> 
        /// 建立Tcp连接后处理过程 
        /// </summary> 
        /// <param name="iar">异步Socket</param> 
        protected virtual void Connected(IAsyncResult iar)
        {
            Socket socket = (Socket)iar.AsyncState;
            try
            {
                socket.EndConnect(iar);
            }
            catch(Exception ex)
            {
                if (this.OnSockErrorEvent != null)
                    this.OnSockErrorEvent(ex.Message);
                socket.Close();
                return;
            }
            //创建新的会话
            _session = new Session(socket);
            _isConnected = true;

            //触发连接建立事件 
            if (this.OnConnectedServerEvent != null)
            {
                this.OnConnectedServerEvent(this, new NetEventArgs(_session));
            }

            //建立连接后应该立即接收数据 
            _session.ClientSocket.BeginReceive(_recvDataBuffer, 0, BUFFERSIZE, SocketFlags.None, new AsyncCallback(RecvData), socket);
        }

        /// <summary> 
        /// 数据接收处理函数 
        /// </summary> 
        /// <param name="iar">异步Socket</param> 
        protected virtual void RecvData(IAsyncResult iar)
        {
            Socket remote = (Socket)iar.AsyncState;

            try
            {
                int recv = remote.EndReceive(iar);

                //正常的退出 
                if (recv == 0)
                {
                    _session.TypeOfExit = Session.ExitType.NormalExit;
                    this._isConnected = false;

                    if (OnDisConnectedServerEvent != null)
                    {
                        OnDisConnectedServerEvent(this, new NetEventArgs(_session));
                    }
                    return;
                }

                //通过事件发布收到的报文 
                 //发布收到数据的事件 
                if (this.OnReceivedDatagramEvent != null)
                {
                    IList<byte[]> recvDatagrams = this.DatagramAnalyze.Disassemble(_session, _recvDataBuffer, recv);
                    foreach (byte[] newDatagram in recvDatagrams)
                    {
                        ICloneable copySession = (ICloneable)_session;//Need Deep Copy.因为需要保证多个不同报文独立存在 
                        Session clientSession = (Session)copySession.Clone();
                        clientSession.Datagram = newDatagram;
                        this.OnReceivedDatagramEvent(this, new NetEventArgs(clientSession)); //发布一个报文消息 
                    }
                }
                //继续接收数据 
                if( _session!=null)
                    _session.ClientSocket.BeginReceive(_recvDataBuffer, 0, BUFFERSIZE, SocketFlags.None, new AsyncCallback(RecvData), _session.ClientSocket);
            }
            catch (SocketException ex)
            {
                //客户端退出 
                if (10054 == ex.ErrorCode)
                {
                    //服务器强制的关闭连接，强制退出 
                    _session.TypeOfExit = Session.ExitType.ExceptionExit;
                    this._isConnected = false;

                    if (OnDisConnectedServerEvent != null)
                    {
                        OnDisConnectedServerEvent(this, new NetEventArgs(_session));
                    }
                }
                else
                {
                    throw (ex);
                }
            }
            catch (ObjectDisposedException ex)
            {
                //这里的实现不够优雅 
                //当调用CloseSession()时,会结束数据接收,但是数据接收 
                //处理中会调用int recv = client.EndReceive(iar); 
                //就访问了CloseSession()已经处置的对象 
                //我想这样的实现方法也是无伤大雅的. 
                if (ex != null)
                {
                    ex = null;
                    //DoNothing; 
                }
            }

        }

        #endregion
    }
}
